package com.theorydance.esoperator.utils;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Cache<K, V> {

	/**
	 * 存放该缓存中的键值对信息
	 */
	private volatile Map<K, Entity> cacheMap;
	/**
	 * 本缓存的有效时长，单位秒（s）
	 */
	private final long duration;
	public Cache(int capacity) {
		// 默认12小时
		this(12*60*60, capacity);
	}
	
	/**
	 * @param duration 本缓存的有效时长，单位秒（s）
	 */
	public Cache(long duration, int capacity) {
		this.duration = duration;
		cacheMap = new ConcurrentHashMap<>(capacity);
		// 将该缓存，添加的守护线程的管理中去（自动定时进行清理无效键值对）
		CacheDaemon.addCacheInstance(this);
	}
	
	/**
	 * 向缓存中添加键值对
	 * @param key 键
	 * @param value 值
	 */
	public void put(@NonNull K key, V value) {
		// 当传递的键，在缓存中存在时，直接替换为新的（这个过程相当于重新设置了缓存的过期时间）
		Entity entity = new Entity(key, value);
		cacheMap.put(key, entity);
	}
	
	/**
	 * 向缓存中添加键值对
	 * @param key 键
	 * @param value 值
	 * @param expireValue 过期时长，配合单位timeUnit
	 * @param timeUnit 过期时间对应的单位：Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND
	 */
	public void put(@NonNull K key, V value, int expireValue, int timeUnit) {
		// 当传递的键，在缓存中存在时，直接替换为新的（这个过程相当于重新设置了缓存的过期时间）
		Entity entity = new Entity(key, value, expireValue, timeUnit);
		cacheMap.put(key, entity);
	}
	
	/**
	 * 清除该缓存中的所有键值对
	 */
	public void clear() {
		cacheMap.clear();
	}
	
	/**
	 * 清除该缓存中指定的键值对
	 */
	public void clear(K key) {
		try {
			if(key != null) {
				cacheMap.remove(key);
			}
		} catch (Exception e) {
			log.error(Tools.getExceptionStr(e));
		}
	}
	
	/**
	 * 调用该方法，遍历缓存中的对象，当过期时，自动进行清除，避免缓存占用内存不释放的情况
	 */
	public void expireAndClear() {
		Set<K> keySet = cacheMap.keySet();
		List<K> list = keySet.stream().collect(Collectors.toList());
		for (int i = 0; i < list.size(); i++) {
			try {
				get(list.get(i));
			}catch (Exception e) {
				log.info(e.getMessage());
			}
		}
	}
	
	/**
	 * 获取有效的缓存值，当缓存过期或不存在对应的缓存时，会抛出异常；说明：当存在对应缓存，但是缓存已过期，则会先执行该键值对删除，然后再抛出异常，表示没有有效的缓存键值对。
	 * @param key 缓存的key
	 * @return 有效的缓存值value
	 * @throws Exception 当缓存过期或不存在对应的缓存时，抛出异常
	 */
	public V get(K key) throws Exception{
		Entity entity = cacheMap.get(key);
		if(entity == null) {
			throw new Exception("未在缓存中存在");
		}
		
		// 判断有效性
		if(entity.isValid()) {
			return entity.value;
		}else {
			// 清空cacheMap中该key对应的数据，注意并发（在清空的时候，重新进行了缓存）
			cacheMap.remove(key, entity);
			throw new Exception("缓存过期，"+entity.toString());
		}
	}
	
	/**
	 * 获取有效的缓存，如果缓存无效或不存在，就自动调用supplier进行缓存数据。（将get进行包装，进行双重校验，简化调用方代码）
	 * @param key
	 * @param supplier 当缓存失效或不存在时，调用
	 * @return
	 * @throws Exception
	 */
	public V doubleCheck(K key, Supplier<V> supplier){
		V value = null;
		try {
			value = get(key);
		} catch (Exception e) {
			synchronized (this) {
				try {
					value = get(key);
				} catch (Exception e1) {
					value = supplier.get();
					put(key, value);
				}
			}
		}
		return value;
	}
	
	/**
	 * 缓存具体对象数据，键值对信息，该键值对中的信息不可修改
	 */
	class Entity{
		
		final K key;
		final V value;
		
		/**
		 * 该键值对的有效时间节点
		 */
		final Date validTime;
		
		Entity(K key,V value) {
			this.key = key;
			this.value = value;
			Date now = new Date();
			this.validTime = new Date(now.getTime() + duration*1000);
		}
		
		/**
		 * 设置键值对以及对应的有效时长
		 * @param key	键
		 * @param value	值
		 * @param expireValue 过期时长，配合单位timeUnit
		 * @param timeUnit 过期时间对应的单位：Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND
		 */
		Entity(K key,V value, int expireValue, int timeUnit) {
			this.key = key;
			this.value = value;
			Calendar c = Calendar.getInstance();
			c.add(timeUnit, expireValue);
			this.validTime = c.getTime();
		}
		
		/**
		 * 缓存是否有效
		 */
		boolean isValid() {
			if(validTime.getTime() < new Date().getTime()) {
				return false;
			}
			return true;
		}
		@Override
		public String toString() {
			try {
				return "缓存key="+key+",value="+value+",validTime="+validTime;
			} catch (Exception e) {
				return null;
			}
		}
	}
	
}
